package fr.esiag.cryptographie;

import java.math.BigInteger;
import java.util.Random;

import javax.management.RuntimeErrorException;

import net.iharder.Base64;

/**
 * Classe conteneur pour RSA.
 * 
 */
public class Rsa {

	/**
	 * Genere un nombre premier pseudo aleatoire
	 * 
	 * @return
	 */
	public static BigInteger genPremier(int bitLength) {
		return BigInteger.probablePrime(bitLength, new Random());
	}

	/**
	 * Bean de cle publique.
	 * 
	 */
	public static class ClePublique {
		private String nom;
		private BigInteger n;
		private BigInteger e;

		public ClePublique(String nom, BigInteger n, BigInteger e) {
			super();
			this.nom = nom;
			this.n = n;
			this.e = e;
		}

		@Override
		public String toString() {
			return "ClePublique [nom=" + nom + ", n=" + n + ", e=" + e + "]";
		}
	}

	/**
	 * Bean de cle privee
	 * 
	 */
	public static class ClePrivee {
		private String nom;
		private BigInteger n;
		private BigInteger d;
		private BigInteger e;

		public ClePrivee(String nom, BigInteger n, BigInteger d, BigInteger e) {
			super();
			this.nom = nom;
			this.n = n;
			this.d = d;
			this.e = e;
		}

		@Override
		public String toString() {
			return "ClePrivee [nom=" + nom + ", n=" + n + ", d=" + d + "]";
		}

		/**
		 * Extrait la cle publique a partir de la cle privee.
		 * 
		 * @return cle publique
		 */
		public ClePublique getClePublique() {
			return new ClePublique(nom, n, e);
		}
	}

	private static BigInteger nextRandomBigInteger(BigInteger n) {
		Random rand = new Random();
		BigInteger result = new BigInteger(n.bitLength(), rand);
		while (result.compareTo(n) >= 0) {
			result = new BigInteger(n.bitLength(), rand);
		}
		return result;
	}

	/**
	 * Genere une cle privee (contenant aussi les information de la cle
	 * publique)
	 * 
	 * @param nom
	 *            nom du tiers
	 * @param bitLength
	 *            taille en bit des premier a generer
	 * @return une cle privee
	 */
	public static ClePrivee genClePrivee(String nom, int bitLength) {
		System.out.println("Generation de cle pour bob");
		BigInteger p = genPremier(bitLength);
		System.out.println("p       = " + p);
		BigInteger q = genPremier(bitLength);
		System.out.println("q       = " + q);
		BigInteger n = p.multiply(q);
		System.out.println("n=(p*q) = " + n);
		BigInteger e = null;

		BigInteger sigman = p.subtract(BigInteger.ONE).multiply(
				q.subtract(BigInteger.ONE));
		System.out.println("sigma n =(p-1*q-1) = " + sigman);
		// recherche d'une position de depart pour la recherche de e en
		// utilisant un random
		BigInteger start = p.max(q).add(BigInteger.ONE);
		BigInteger diff = sigman.subtract(start);
		start = start.add(nextRandomBigInteger(diff));
		for (BigInteger i = start; i.compareTo(sigman) == -1; i = i
				.add(BigInteger.ONE)) {
			if (sigman.gcd(i).equals(BigInteger.ONE)) {
				e = i;
				break;
			}
		}
		if (e == null) {
			throw new RuntimeException("pas de pgcd");
		}
		System.out.println("e= p,q<e<sigmaN et pgcd(sigmaN,e)=1 = " + e);

		BigInteger d = e.modInverse(sigman);
		System.out.println("d= 1/E mod sigmaN = " + d);
		ClePrivee priv = new ClePrivee(nom, n, d, e);
		return priv;
	}

	/**
	 * Chiffre un long (64bit).
	 * 
	 * @param bloc
	 *            un long (64 bit)
	 * @param pub
	 *            cle publique a utiliser
	 * @return
	 */
	public static byte[] chiffreBloc(byte[] bloc, ClePublique pub) {
		if (bloc.length*8>=pub.n.bitLength()){
			throw new RuntimeException("bloc trop grand ("+ bloc.length*8+ " bits), ne doit pas depasser la taille de n (" + pub.n.bitLength()+ " bits)");
		}
		BigInteger clair = new BigInteger(bloc);
		BigInteger chiffre =  clair.modPow(pub.e, pub.n);
		return chiffre.toByteArray();
	}

	/**
	 * DeChiffre un long (64bit).
	 * 
	 * @param blocChiffre
	 *            un long
	 * @param priv
	 *            la cle privee a utiliser
	 * @return
	 */
	public static byte[] dechiffreBloc(byte[] blocChiffre, ClePrivee priv) {
		BigInteger chiffre = new BigInteger(blocChiffre);
		BigInteger clair = chiffre.modPow(priv.d, priv.n);
		return clair.toByteArray();
	}

	public static void main(String[] args) {
		ClePrivee priv = genClePrivee("Bob",100);
		ClePublique pub = priv.getClePublique();
		System.out.println(priv);
		System.out.println(pub);

		String clair = "salut les gars !";
		System.out.println("clair    : "+clair);	
		byte[] chiffre = chiffreBloc(clair.getBytes(), pub);
		System.out.println("chiffre  : "+Base64.encodeBytes(chiffre));
		
		System.out.println("dechiffre: " + new String(dechiffreBloc(chiffre, priv)));
	}
}
